package com.tcm.service.impl;

import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tcm.common.enums.CachePrefix;
import com.tcm.entity.*;
import com.tcm.entity.bo.StudentExcel;
import com.tcm.entity.dto.ClazzUpdateDto;
import com.tcm.entity.dto.param.StudentPageParam;
import com.tcm.entity.dto.StudentUpdateDto;
import com.tcm.entity.vo.BatchImportResult;
import com.tcm.entity.resp.PageR;
import com.tcm.entity.vo.StudentListResult;
import com.tcm.exception.DatabaseOperationException;
import com.tcm.mapper.RoleMapper;
import com.tcm.mapper.RoleUserMapper;
import com.tcm.mapper.StudentMapper;
import com.tcm.service.*;
import com.tcm.utils.ExcelExportUtil;
import com.tcm.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 学生管理业务实现类
 * @author Guqier
 * @version 1.0
 * @date 2023/7/2 16:59
 */
@Slf4j
@Service
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements StudentService {

    private static final String STUDENT_ROLE_KEY = "student";

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private RoleUserMapper roleUserMapper;

    @Autowired
    private ClazzService clazzService;

    @Autowired
    private UserService userService;

    @Autowired
    private OrganizationService orgService;

    @Autowired
    private CodeService codeService;

    @Autowired
    private RedisUtil redisUtil;

    private static final int STUDENT_TYPE = 2;


    @Override
    @Transactional
    public boolean addStudentInfo(StudentUpdateDto studentUpdateDto, Long currUserId) throws DatabaseOperationException{
        User user1 = userService.getUserByAccount(studentUpdateDto.getAccount());
        if (user1 != null){
            return false;// 账号已存在
        }
        // 保存学生信息
        Student student = new Student();
        BeanUtils.copyProperties(studentUpdateDto, student);
        Clazz clazz = clazzService.getById(student.getClazzId());
        student.setClazzName(clazz.getName());
        student.setCollegeId(clazz.getCollegeId());
        student.setMajorId(clazz.getMajorId());
        student.setGradeId(clazz.getGradeId());
        student.setScore(0);
        student.setCreateUser(currUserId);
        student.setUpdateUser(currUserId);
        boolean stuSave = this.save(student);
        // 保存用户信息
        User user = userService.getNewFillInfoUser(student.getAccount(), student.getName(),
                student.getName(), STUDENT_TYPE, studentUpdateDto.getGender(), null, null, currUserId);
        boolean userSave = userService.save(user);
        // 保存角色关系
        LambdaQueryWrapper<Role> roleLambdaQueryWrapper = new LambdaQueryWrapper<>();
        roleLambdaQueryWrapper.eq(Role::getRoleKey, STUDENT_ROLE_KEY);
        Role role = roleMapper.selectOne(roleLambdaQueryWrapper);
        RoleUser roleUser = new RoleUser();
        roleUser.setUserId(user.getId());
        roleUser.setRoleId(role.getId());
        roleUser.setCreateUser(currUserId);
        roleUser.setUpdateUser(currUserId);
        int insert = roleUserMapper.insert(roleUser);
        if (!stuSave || !userSave || insert != 1){
            throw new DatabaseOperationException("数据库操作失败");
        }
        return true;
    }

    /**
     * 分页获取学生信息
     * @param studentPageParam 学生分页查询信息数据封装类
     * @return 学生分页数据
     */
    @Override
    public PageR<StudentListResult> getStudentListByPage(StudentPageParam studentPageParam){
        Page<Student> page = new Page<>(studentPageParam.getPage(), studentPageParam.getPageSize());
        page.addOrder(OrderItem.desc("update_time"));
        LambdaQueryWrapper<Student> queryWrapper = buildStudentQueryWrapper(studentPageParam);

        this.page(page, queryWrapper);
        List<Student> list = page.getRecords();
        // 获取组织机构数据
        List<Organization> orgList = orgService.list();
        List<Code> codeList = codeService.list();
        // 封装响应数据
        List<StudentListResult> res = transferStudentToListResult(list);
        PageR<StudentListResult> pageR = new PageR<>();
        BeanUtils.copyProperties(page, pageR, "records");
        pageR.setRecords(res);
        return pageR;
    }

    /**
     * 将学生对象转换为数据展示类
     * @param students 学生对象
     * @return 数据展示类
     */
    @Override
    public List<StudentListResult> transferStudentToListResult(List<Student> students){
        // 获取组织机构数据
        List<Organization> orgList = orgService.list();
        List<Code> codeList = codeService.list();
        return students.stream().map(student -> {
            StudentListResult studentListResult = new StudentListResult();
            BeanUtils.copyProperties(student, studentListResult);
            studentListResult.setCollegeId(student.getCollegeId());
            studentListResult.setMajorId(student.getMajorId());
            studentListResult.setGradeId(student.getGradeId());
            orgList.stream().filter(o -> o.getId().equals(student.getCollegeId())).findFirst().ifPresent(organization -> {
                studentListResult.setCollegeName(organization.getName());
            });
            orgList.stream().filter(o -> o.getId().equals(student.getMajorId())).findFirst().ifPresent(organization -> {
                studentListResult.setMajorName(organization.getName());
            });
            codeList.stream().filter(c -> c.getId().equals(student.getGradeId())).findFirst().ifPresent(code -> {
                studentListResult.setGradeName(code.getName());
            });
            User user = userService.getUserByAccount(student.getAccount());
            studentListResult.setGender(user != null ? user.getGender() : null);
            return studentListResult;
        }).collect(Collectors.toList());
    }

    /**
     * 修改学生信息
     * @param studentUpdateDto 学生更新信息封装类
     * @param currUserId 当前登录用户信息
     * @return 处理结果，true为成功
     * @throws DatabaseOperationException 数据库操作异常
     */
    @Override
    @Transactional
    public boolean modifyStudentInfo(StudentUpdateDto studentUpdateDto, Long currUserId)
            throws DatabaseOperationException{
        Student student = this.getById(studentUpdateDto.getId());
        if (student == null) {
            return false;
        }
        // 更新学生信息
        student.setName(studentUpdateDto.getName());
        Clazz clazz = clazzService.getById(studentUpdateDto.getClazzId());
        student.setClazzId(clazz.getId());
        student.setClazzName(clazz.getName());
        student.setCollegeId(clazz.getCollegeId());
        student.setMajorId(clazz.getMajorId());
        student.setGradeId(clazz.getGradeId());
        student.setUpdateUser(currUserId);
        boolean res = this.updateById(student);

        // 更新用户信息
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getAccount, student.getAccount());
        User one = userService.getOne(queryWrapper);
        one.setGender(studentUpdateDto.getGender());
        boolean res1 = userService.updateById(one);

        if (!res || !res1){
            throw new DatabaseOperationException("数据更新异常");
        }
        redisUtil.deleteObject(CachePrefix.PC_USER_INFO.getCacheKey(one.getAccount()));
        return true;
    }

    /**
     * 删除学生信息
     * @param ids id列表
     * @param currUserId 当前登录用户ID
     * @return 执行结果，true为成功
     */
    @Override
    @Transactional
    public boolean removeStudentInfo(List<String> ids, Long currUserId){
        int cnt = 0;
        for (String id : ids) {
            Student student = this.getById(id);
            if (student == null) {
                continue;
            }
            LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(User::getAccount, student.getAccount());
            User one = userService.getOne(queryWrapper);
            if (one == null){
                continue;
            }
            student.setUpdateUser(currUserId);
            one.setUpdateUser(currUserId);
            this.updateById(student);
            userService.updateById(one);
            // 删除角色关系
            LambdaQueryWrapper<RoleUser> roleUserLambdaQueryWrapper = new LambdaQueryWrapper<>();
            roleUserLambdaQueryWrapper.eq(RoleUser::getUserId, one.getId());
            List<RoleUser> roleUsers = roleUserMapper.selectList(roleUserLambdaQueryWrapper);
            for (RoleUser roleUser : roleUsers) {
                roleUser.setUpdateUser(currUserId);
                roleUserMapper.updateById(roleUser);
                roleUserMapper.deleteById(roleUser.getId());
            }
            if (this.removeById(id) && userService.removeById(one.getId())){
                cnt++;
            }
            redisUtil.deleteObject(CachePrefix.PC_USER_INFO.getCacheKey(one.getAccount()));
        }
        if (cnt != ids.size()){
            throw new DatabaseOperationException("数据库更新失败");
        }
        return true;
    }

    /**
     * 批量导入学生信息信息校验
     * @param file       excel文件
     * @param currUserId 当前登录用户ID
     * @return 上传结果，true为成功。
     */
    @Override
    @Transactional
    public BatchImportResult<StudentExcel> importStudentInfo(MultipartFile file, Long currUserId) throws IOException {
        // 解析Excel文件
        ExcelReader reader = ExcelUtil.getReader(file.getInputStream());
        reader.addHeaderAlias("学号", "account");
        reader.addHeaderAlias("姓名", "name");
        reader.addHeaderAlias("性别", "gender");
        reader.addHeaderAlias("学院", "college");
        reader.addHeaderAlias("专业", "major");
        reader.addHeaderAlias("年级", "grade");
        reader.addHeaderAlias("班级", "clazz");
        List<StudentExcel> studentInfos = reader.readAll(StudentExcel.class);
        // 获取学院列表
        List<Organization> collegeList = orgService.getCollegeList();
        // 获取专业列表
        List<Organization> majorList = orgService.getMajorList();
        //获取年级列表
        List<Code> gradeList = codeService.getGradeList();
        // 构造结果集
        BatchImportResult<StudentExcel> results = new BatchImportResult<>();
        List<StudentExcel> success = new ArrayList<>();// 成功结果集
        List<StudentExcel> fail = new ArrayList<>();// 失败结果集
        List<StudentUpdateDto> students = new ArrayList<>();
        // 校验并存储信息到学生对象
        for (StudentExcel studentInfo : studentInfos) {
            User one1 = userService.getUserByAccount(studentInfo.getAccount());
            if (one1 != null){
                // 储存到校验失败结果集
                studentInfo.setMsg("账号已存在");
                fail.add(studentInfo);
                continue;
            }
            StudentUpdateDto studentDto = new StudentUpdateDto();
            studentDto.setAccount(studentInfo.getAccount());
            studentDto.setName(studentInfo.getName());
            studentDto.setGender(studentInfo.getGender().equals("男") ? 1 : 0);

            // 校验并设置学院ID
            Optional<Organization> college = collegeList.stream().filter(i -> i.getName().equals(studentInfo.getCollege())).findFirst();
            if (!college.isPresent()) {
                // 储存到校验失败结果集
                studentInfo.setMsg("找不到该学院");
                fail.add(studentInfo);
                continue;
            }
            // 校验并设置专业ID
            Optional<Organization> major = majorList.stream().filter(i -> i.getName().equals(studentInfo.getMajor())).findFirst();
            if (!major.isPresent()) {
                // 储存到校验失败结果集
                studentInfo.setMsg("找不到该专业");
                fail.add(studentInfo);
                continue;
            }
            // 校验年级
            Optional<Code> grade = gradeList.stream().filter(i -> i.getName().equals(studentInfo.getGrade())).findFirst();
            if (!grade.isPresent()) {
                // 储存到校验失败结果集
                studentInfo.setMsg("找不到该年级");
                fail.add(studentInfo);
                continue;
            }
            // 校验并设置班级ID
            LambdaQueryWrapper<Clazz> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Clazz::getName, studentInfo.getClazz());
            Clazz one = clazzService.getOne(queryWrapper);
            if (one == null) {
                // 如果班级不存在则添加班级
                ClazzUpdateDto clazzUpdateDto = new ClazzUpdateDto();
                clazzUpdateDto.setName(studentInfo.getClazz());
                clazzUpdateDto.setCollegeId(college.get().getId());
                clazzUpdateDto.setMajorId(major.get().getId());
                clazzUpdateDto.setGradeId(grade.get().getId());
                boolean res = clazzService.addClazzInfo(clazzUpdateDto, currUserId);
                if (!res) {
                    studentInfo.setMsg("创建班级失败");
                    fail.add(studentInfo);
                    continue;
                }
                LambdaQueryWrapper<Clazz> clazzLambdaQueryWrapper = new LambdaQueryWrapper<>();
                clazzLambdaQueryWrapper.eq(Clazz::getName, studentInfo.getClazz());
                one = clazzService.getOne(clazzLambdaQueryWrapper);
            }
            studentDto.setClazzId(one.getId());
            // 保存到结果集
            students.add(studentDto);
            studentInfo.setMsg("校验通过");
            success.add(studentInfo);
        }
        results.setUuid(UUID.randomUUID().toString());
        results.setTotal(studentInfos.size());
        results.setSuccessCount(success.size());
        results.setSuccessList(success);
        results.setFailCount(fail.size());
        results.setFailList(fail);
        // 存入缓存，等待前端确认，超过5分钟自动清除
        redisUtil.setCacheObject(CachePrefix.PC_IMPORT_STUDENT_RES.getCacheKey(results.getUuid()), results, 5, TimeUnit.MINUTES);
        redisUtil.setCacheObject(CachePrefix.PC_IMPORT_STUDENT_DTO.getCacheKey(results.getUuid()), students, 5, TimeUnit.MINUTES);
        log.info("学生信息上传ID：{} 存入缓存", results.getUuid());
        return results;
    }

    /**
     * 确认导入学生信息
     * @param uuid 导入uuid
     * @return 导入结果，true为成功
     */
    @Override
    @Transactional
    public boolean confirmBatchStudentInfo(String uuid, Long currUserId){
        // 从缓存中取出数据
        List<StudentUpdateDto> students = redisUtil.getCacheObject(CachePrefix.PC_IMPORT_STUDENT_DTO.getCacheKey(uuid));
        if (students == null) {
            return false;
        }
        int cnt = 0;
        for (StudentUpdateDto student : students) {
            boolean res = addStudentInfo(student, currUserId);
            if (res){
                cnt++;
            }
        }
        // 保存数据到数据库
        if (cnt != students.size()){
            throw new DatabaseOperationException("数据库保存失败");
        }
        // 清除缓存
        redisUtil.deleteObject(CachePrefix.PC_IMPORT_STUDENT_DTO.getCacheKey(uuid));
        return true;
    }

    /**
     * 导出导入失败名单
     * @param uuid 导入uuid
     * @param response http响应对象
     */
    @Override
    public void exportImportFailedStudentInfo(String uuid, HttpServletResponse response) throws Exception {
        // 从缓存中获取数据
        BatchImportResult<StudentExcel> results = redisUtil.getCacheObject(CachePrefix.PC_IMPORT_STUDENT_RES.getCacheKey(uuid));
        if (results == null) {
            throw new Exception("缓存不存在");
        }
        exportStudentCommon(response, results.getFailList());
    }

    /**
     * 根据检索信息导出学生列表
     * @param studentPageParam 检索信息
     * @param response http 响应对象
     * @return 导出结果，true为成功
     */
    @Override
    public boolean exportStudentInfo(StudentPageParam studentPageParam, HttpServletResponse response){
        // 构造查询条件
        LambdaQueryWrapper<Student> queryWrapper = buildStudentQueryWrapper(studentPageParam);
        List<Student> list = this.list(queryWrapper);
        List<StudentExcel> excels = new ArrayList<>();
        // 准备数据
        List<Organization> organizations = orgService.list();
        List<Code> codes = codeService.list();
        List<Clazz> clazzs = clazzService.list();
        // 构造导出结果
        for (Student student : list) {
            StudentExcel studentExcel = new StudentExcel();
            studentExcel.setAccount(student.getAccount());
            studentExcel.setName(student.getName());
            User user = userService.getUserByAccount(student.getAccount());
            studentExcel.setGender(user.getGender() == 1 ? "男" : "女");
            organizations.stream().filter(organization -> organization.getId().equals(student.getCollegeId())).findFirst().ifPresent(organization -> studentExcel.setCollege(organization.getName()));
            organizations.stream().filter(organization -> organization.getId().equals(student.getMajorId())).findFirst().ifPresent(organization -> studentExcel.setMajor(organization.getName()));
            codes.stream().filter(code -> code.getId().equals(student.getGradeId())).findFirst().ifPresent(code -> studentExcel.setGrade(code.getName()));
            clazzs.stream().filter(clazz -> clazz.getId().equals(student.getClazzId())).findFirst().ifPresent(clazz -> studentExcel.setClazz(clazz.getName()));
            studentExcel.setMsg("正常导出");
            excels.add(studentExcel);
        }
        // 响应导出
        try {
            exportStudentCommon(response, excels);
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    /**
     * 通用学生信息导出
     * @param response 响应对象
     * @param excels 学生对象列表
     * @throws Exception 异常
     */
    public void exportStudentCommon(HttpServletResponse response, List<StudentExcel> excels) throws Exception {
        // 设置导出列名
        ExcelWriter writer = ExcelUtil.getWriter();
        writer.addHeaderAlias("account", "学号");
        writer.addHeaderAlias("name", "姓名");
        writer.addHeaderAlias("gender", "性别");
        writer.addHeaderAlias("college", "学院");
        writer.addHeaderAlias("major", "专业");
        writer.addHeaderAlias("grade", "年级");
        writer.addHeaderAlias("clazz", "班级");
        writer.addHeaderAlias("msg", "导出/导出状态");
        // 响应数据
        writer.write(excels, true);

        ExcelExportUtil.commonExport(writer, response);
    }

    /**
     * 构造学生查询LambdaQueryWrapper
     * @param studentPageParam 分页查询学生参数
     * @return LambdaQueryWrapper
     */
    public LambdaQueryWrapper<Student> buildStudentQueryWrapper(StudentPageParam studentPageParam){
        LambdaQueryWrapper<Student> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(studentPageParam.getCollegeId() != null, Student::getCollegeId, studentPageParam.getCollegeId());
        queryWrapper.eq(studentPageParam.getMajorId() != null, Student::getMajorId, studentPageParam.getMajorId());
        queryWrapper.eq(studentPageParam.getGradeId() != null, Student::getGradeId, studentPageParam.getGradeId());
        queryWrapper.eq(studentPageParam.getClazzId() != null, Student::getClazzId, studentPageParam.getClazzId());
        queryWrapper.like(StringUtils.hasText(studentPageParam.getName()), Student::getName, studentPageParam.getName());
        queryWrapper.like(StringUtils.hasText(studentPageParam.getAccount()), Student::getAccount, studentPageParam.getAccount());
        return queryWrapper;
    }

    /**
     * 通过账号获取学生信息
     * @param account 账号
     * @return 学生信息
     */
    @Override
    public Student getStudentByAccount(String account){
        LambdaQueryWrapper<Student> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Student::getAccount, account);
        return this.getOne(queryWrapper);
    }
}
